package de.bloxel.ui;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import com.jme3.app.Application;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.font.BitmapFont;
import com.jme3.input.FlyByCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeSystem;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory;

import de.bloxel.core.Bloxel;
import de.bloxel.core.CrossHair;

/**
 * @author klaus hauschildt
 * @author patrick lorenz
 * @author stephan dorer
 * @author andreas.hoehmann@gmx.de
 * 
 * @since 0.0.1
 */
public class BloxelApplication extends Application {

  private static final Logger logger = Logger.getLogger(BloxelApplication.class.getName());

  private final Node rootNode = new Node("Root Node");
  private final Node guiNode = new Node("Gui Node");

  private BitmapFont guiFont;

  private Node bloxelsNode;

  private BulletAppState bulletAppState;

  public BloxelApplication() {
    setSettings(loadSettings());
  }

  void addBloxel(final Vector3f targetPosition) {
    // TODO pruefen ob an dieser neuen stelle bereits ein bloxel existiert
    final Bloxel bloxel = new Bloxel(targetPosition, assetManager);
    final Geometry bloxelGeometry = bloxel.getGeometry();
    bloxelGeometry.setShadowMode(ShadowMode.CastAndReceive);
    bloxelGeometry.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(1f, 1f, 1f)), 0f));
    // bloxelGeometry.getControl(RigidBodyControl.class).setKinematic(false);
    bloxelGeometry.getControl(RigidBodyControl.class).setFriction(0.6f);
    bloxelsNode.attachChild(bloxelGeometry);
    getPhysicsSpace().add(bloxelGeometry);
  }

  Node getBloxelsNode() {
    return bloxelsNode;
  }

  private PhysicsSpace getPhysicsSpace() {
    return bulletAppState.getPhysicsSpace();
  }

  Node getRootNode() {
    return rootNode;
  }

  public void initArena() {
    // sky
    final Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg");
    final Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg");
    final Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg");
    final Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg");
    final Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg");
    final Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg");
    final Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
    rootNode.attachChild(sky);

    // container for all bloxels
    bloxelsNode = new Node("Bloxels");
    rootNode.attachChild(bloxelsNode);

    // floor
    final Box floorBox = new Box(Vector3f.ZERO, 1f, 1f, 1f);
    floorBox.scaleTextureCoordinates(new Vector2f(1f, 1f));
    final Material mat3 = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md");
    final TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.png");
    key3.setGenerateMips(true);
    final Texture tex3 = assetManager.loadTexture(key3);
    tex3.setWrap(WrapMode.Clamp);
    mat3.setTexture("ColorMap", tex3);
    for (int x = -15; x < 15; x++) {
      for (int z = -15; z < 15; z++) {
        final Geometry floorGeometry = new Geometry("floor", floorBox);
        floorGeometry.setMaterial(mat3);
        floorGeometry.setShadowMode(ShadowMode.Receive);
        floorGeometry.setLocalTranslation(x * 2, 0, z * 2);
        floorGeometry.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(1f, 1f, 1f)), 0));
        bloxelsNode.attachChild(floorGeometry);
        getPhysicsSpace().add(floorGeometry);
      }
    }
  }

  /**
   * Init a wasd and mouse movement.
   */
  private void initCameras() {
    final FlyByCamera flyCam = new FlyByCamera(cam);
    flyCam.setMoveSpeed(20f);
    flyCam.registerWithInput(inputManager);
    cam.setLocation(new Vector3f(0, 10f, 0));
    cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));
    cam.setParallelProjection(false);
    cam.setFrustumFar(100f);
  }

  private void initCrossHair() {
    // XXX eigentlich brauchen wir die instanz nicht ... nur um das fadenkreuz "einmal" zu initialisieren
    new CrossHair(guiNode, guiFont, getAssetManager(), settings);
  }

  @Override
  public void initialize() {

    super.initialize();

    viewPort.attachScene(rootNode);

    bulletAppState = new BulletAppState();
    bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
    stateManager.attach(bulletAppState);

    initCameras();
    initArena();
    initLights();
    initCrossHair();
    initKeys();

    guiNode.setQueueBucket(Bucket.Gui);
    guiNode.setCullHint(CullHint.Never);
    guiViewPort.attachScene(guiNode);
    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
  }

  private void initKeys() {
    inputManager.addMapping(BloxelActionController.ATTACH_ACTION, new KeyTrigger(KeyInput.KEY_SPACE), new MouseButtonTrigger(0));
    inputManager.addMapping(BloxelActionController.DETACH_ACTION, new MouseButtonTrigger(1));
    inputManager.addMapping(BloxelActionController.EXIT_ACTION, new KeyTrigger(KeyInput.KEY_ESCAPE));
    inputManager.addListener(new BloxelActionController(this), new String[] { BloxelActionController.ATTACH_ACTION,
        BloxelActionController.DETACH_ACTION, BloxelActionController.EXIT_ACTION });
  }

  private void initLights() {
    final DirectionalLight sun = new DirectionalLight();
    sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal());
    sun.setColor(ColorRGBA.White);
    rootNode.addLight(sun);
  }

  private AppSettings loadSettings() {
    final File settingsFile = new File("bloxel.settings");
    if (!settingsFile.exists()) {
      final AppSettings appSettings = new AppSettings(true);
      appSettings.setResolution(800, 600);
      appSettings.setTitle("Bloxel");
      // appSettings.setRenderer(AppSettings.JOGL);
      appSettings.setRenderer(AppSettings.LWJGL_OPENGL2);
      appSettings.setSettingsDialogImage("/de/bloxel/ui/settingsDialogImage.png");
      JmeSystem.showSettingsDialog(appSettings);
      FileOutputStream settingOutputStream = null;
      try {
        settingsFile.createNewFile();
        settingOutputStream = FileUtils.openOutputStream(settingsFile);
        appSettings.save(settingOutputStream);
      } catch (final IOException e) {
        logger.log(Level.SEVERE, "Unable to write settings file.", e);
      } finally {
        IOUtils.closeQuietly(settingOutputStream);
      }
      return appSettings;
    } else {
      FileInputStream settingsInputStream = null;
      try {
        settingsInputStream = FileUtils.openInputStream(settingsFile);
        final AppSettings loadedAppSettings = new AppSettings(false);
        loadedAppSettings.load(settingsInputStream);
        return loadedAppSettings;
      } catch (final IOException e) {
        logger.log(Level.SEVERE, "Unable to read settings file.", e);
      } finally {
        IOUtils.closeQuietly(settingsInputStream);
      }
    }
    return new AppSettings(true);
  }

  @Override
  public void update() {
    super.update(); // makes sure to execute AppTasks
    if (speed == 0 || paused) {
      return;
    }

    final float tpf = timer.getTimePerFrame() * speed;

    // update states
    stateManager.update(tpf);

    // simple update and root node
    rootNode.updateLogicalState(tpf);
    guiNode.updateLogicalState(tpf);
    rootNode.updateGeometricState();
    guiNode.updateGeometricState();

    // render states
    stateManager.render(renderManager);
    renderManager.render(tpf);
    stateManager.postRender();
  }
}
